/*
* Copyright (c) 2009, UhLeeKa
* Licensed under the GPL license:
*     http://www.gnu.org/licenses/gpl.html
* Author Website: 
*     http://www.uhleeka.com
*
* Description: 
*     A bubble-styled tooltip extension
*      - multiple tips on a page
*      - multiple tips per jQuery element 
*      - tips open outward in four directions:
*         - up
*         - down
*         - left
*         - right
*      - tips can be: 
*         - anchored to the triggering jQuery element
*         - absolutely positioned
*         - opened at the current mouse coordinates
*         - anchored to a specified jQuery element
*      - IE png transparency is handled via filters
*/
; (function($) {
	var bindIndex = 0;
	$.fn.extend({
		bubbletip: function(tip, options) {
			var _this, _tip, _options, _calc, _timeoutAnimate, _timeoutRefresh, _isActive, _isHiding, _wrapper, _bindIndex;

			_this = $(this);
			_tip = $(tip);
			_options = {
				positionAt: 'element', // element | body | mouse
				positionAtElement: _this,
				offsetTop: 0,
				offsetLeft: 0,
				deltaPosition: 30,
				deltaDirection: 'up', // direction: up | down | left | right
				mouseoutDelay: 500,
				animationDuration: 250,
				animationEasing: 'swing', // linear | swing
				bindShow: 'mouseover', // mouseover | focus | click | etc.
				bindHide: 'mouseout', // mouseout | blur | etc.
			};
			if (options) {
				_options = $.extend(_options, options);
			}
			// calculated values
			_calc = {
				top: 0,
				left: 0,
				delta: 0,
				mouseTop: 0,
				mouseLeft: 0,
				tipHeight: 0
			};
			_timeoutAnimate = null;
			_timeoutRefresh = null;
			_isActive = false;
			_isHiding = false;
			_bindIndex = bindIndex++;  // for window.resize namespace binding

			// validate _options
			if (!_options.positionAt.match(/^element|body|mouse$/i)) {
				_options.positionAt = 'element';
			}
			if (!_options.deltaDirection.match(/^up|down|left|right$/i)) {
				_options.deltaDirection = 'up';
			}

			// create the wrapper table element
			if (_options.deltaDirection.match(/^up$/i)) {
				_wrapper = $('<table class="bubbletip" cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td class="bt-top"></td><td class="bt-topright"></td></tr><tr><td class="bt-left"></td><td class="bt-content"></td><td class="bt-right"></td></tr><tr><td class="bt-bottomleft"></td><td><table class="bt-bottom" cellspacing="0" cellpadding="0"><tr><th></th><td><div></div></td><th></th></tr></table></td><td class="bt-bottomright"></td></tr></tbody></table>');
			} else if (_options.deltaDirection.match(/^down$/i)) {
				_wrapper = $('<table class="bubbletip" cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td><table class="bt-top" cellspacing="0" cellpadding="0"><tr><th></th><td><div></div></td><th></th></tr></table></td><td class="bt-topright"></td></tr><tr><td class="bt-left"></td><td class="bt-content"></td><td class="bt-right"></td></tr><tr><td class="bt-bottomleft"></td><td class="bt-bottom"></td><td class="bt-bottomright"></td></tr></tbody></table>');
			} else if (_options.deltaDirection.match(/^left$/i)) {
				_wrapper = $('<table class="bubbletip" cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td class="bt-top"></td><td class="bt-topright"></td></tr><tr><td class="bt-left"></td><td class="bt-content"></td><td class="bt-right-tail"><div class="bt-right"></div><div class="bt-right-tail"></div><div class="bt-right"></div></td></tr><tr><td class="bt-bottomleft"></td><td class="bt-bottom"></td><td class="bt-bottomright"></td></tr></tbody></table>');
			} else if (_options.deltaDirection.match(/^right$/i)) {
				_wrapper = $('<table class="bubbletip" cellspacing="0" cellpadding="0"><tbody><tr><td class="bt-topleft"></td><td class="bt-top"></td><td class="bt-topright"></td></tr><tr><td class="bt-left-tail"><div class="bt-left"></div><div class="bt-left-tail"></div><div class="bt-left"></div></td><td class="bt-content"></td><td class="bt-right"></td></tr><tr><td class="bt-bottomleft"></td><td class="bt-bottom"></td><td class="bt-bottomright"></td></tr></tbody></table>');
			}

			// append the wrapper to the document body
			_wrapper.appendTo('body');

			// apply IE filters to _wrapper elements
			if ((/msie/.test(navigator.userAgent.toLowerCase())) && (!/opera/.test(navigator.userAgent.toLowerCase()))) {
				$('*', _wrapper).each(function() {
					var image = $(this).css('background-image');
					if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) {
						image = RegExp.$1;
						$(this).css({
							'backgroundImage': 'none',
							'filter': 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=' + ($(this).css('backgroundRepeat') == 'no-repeat' ? 'crop' : 'scale') + ', src=\'' + image + '\')'
						}).each(function() {
							var position = $(this).css('position');
							if (position != 'absolute' && position != 'relative')
								$(this).css('position', 'relative');
						});
					}
				});
			}

			// move the tip element into the content section of the wrapper
			$('.bt-content', _wrapper).append(_tip);
			// show the tip (in case it is hidden) so that we can calculate its dimensions
			_tip.show();
			// handle left|right delta
			if (_options.deltaDirection.match(/^left|right$/i)) {
				// tail is 40px, so divide height by two and subtract 20px;
				_calc.tipHeight = parseInt(_tip.height() / 2);
				// handle odd integer height
				if ((_tip.height() % 2) == 1) {
					_calc.tipHeight++;
				}
				_calc.tipHeight = (_calc.tipHeight < 20) ? 1 : _calc.tipHeight - 20;
				if (_options.deltaDirection.match(/^left$/i)) {
					$('div.bt-right', _wrapper).css('height', _calc.tipHeight + 'px');
				} else {
					$('div.bt-left', _wrapper).css('height', _calc.tipHeight + 'px');
				}
			}
			// set the opacity of the wrapper to 0
			_wrapper.css('opacity', 0);
			// execute initial calculations
			_Calculate();

			// handle window.resize
			$(window).bind('resize.bubbletip' + _bindIndex, function() {
				if (_timeoutRefresh) {
					clearTimeout(_timeoutRefresh);
				} else {
					_wrapper.hide();
				}
				_timeoutRefresh = setTimeout(function() {
					_Calculate();
					_wrapper.show();
				}, 250);
			});

			// handle mouseover and mouseout events
			$([_wrapper.get(0), this.get(0)]).bind(_options.bindShow, function(e) {
				if (_timeoutAnimate) {
					clearTimeout(_timeoutAnimate);
				}
				if (_isActive) {
					return;
				}
				_isActive = true;
				if (_isHiding) {
					_wrapper.stop(true, false);
				}

				var animation;

				if (_options.positionAt.match(/^element|body$/i)) {
					if (_options.deltaDirection.match(/^up|down$/i)) {
						if (!_isHiding) {
							_wrapper.css('top', parseInt(_calc.top + _calc.delta) + 'px');
						}
						animation = { 'opacity': 1, 'top': _calc.top + 'px' };
					} else {
						if (!_isHiding) {
							_wrapper.css('left', parseInt(_calc.left + _calc.delta) + 'px');
						}
						animation = { 'opacity': 1, 'left': _calc.left + 'px' };
					}
				} else {
					if (_options.deltaDirection.match(/^up|down$/i)) {
						if (!_isHiding) {
							_calc.mouseTop = e.pageY + _calc.top;
							_wrapper.css({ 'top': parseInt(_calc.mouseTop + _calc.delta) + 'px', 'left': parseInt(e.pageX - (_wrapper.width() / 2)) + 'px' });
						}
						animation = { 'opacity': 1, 'top': _calc.mouseTop + 'px' };
					} else {
						if (!_isHiding) {
							_calc.mouseLeft = e.pageX + _calc.left;
							_wrapper.css({ 'left': parseInt(_calc.mouseLeft + _calc.delta) + 'px', 'top': parseInt(e.pageY - (_wrapper.height() / 2)) + 'px' });
						}
						animation = { 'opacity': 1, 'left': _calc.left + 'px' };
					}
				}
				_isHiding = false;
				_wrapper.show();
				_wrapper.animate(animation, _options.animationDuration, _options.animationEasing, function() {
					_wrapper.css('opacity', '');
					_isActive = true;
				});

				return false;
			}).bind(_options.bindHide, function() {
				if (_timeoutAnimate) {
					clearTimeout(_timeoutAnimate);
				}
				_timeoutAnimate = setTimeout(function() {
					var animation;

					_isActive = false;
					_isHiding = true;
					if (_options.positionAt.match(/^element|body$/i)) {
						if (_options.deltaDirection.match(/^up|down$/i)) {
							animation = { 'opacity': 0, 'top': parseInt(_calc.top - _calc.delta) + 'px' };
						} else {
							animation = { 'opacity': 0, 'left': parseInt(_calc.left - _calc.delta) + 'px' };
						}
					} else {
						if (_options.deltaDirection.match(/^up|down$/i)) {
							animation = { 'opacity': 0, 'top': parseInt(_calc.mouseTop - _calc.delta) + 'px' };
						} else {
							animation = { 'opacity': 0, 'left': parseInt(_calc.mouseLeft - _calc.delta) + 'px' };
						}
					}
					_wrapper.animate(animation, _options.animationDuration, _options.animationEasing, function() {
						_wrapper.hide();
						_isHiding = false;
					});

				}, _options.mouseoutDelay);

				return false;
			});

			function _Calculate() {
				// calculate values
				if (_options.positionAt.match(/^element$/i)) {
					var offset = _options.positionAtElement.offset();
					if (_options.deltaDirection.match(/^up$/i)) {
						_calc.top = offset.top + _options.offsetTop - _wrapper.height();
						_calc.left = offset.left + _options.offsetLeft + ((_options.positionAtElement.width() - _wrapper.width()) / 2);
						_calc.delta = _options.deltaPosition;
					} else if (_options.deltaDirection.match(/^down$/i)) {
						_calc.top = offset.top + _options.positionAtElement.height() + _options.offsetTop;
						_calc.left = offset.left + _options.offsetLeft + ((_options.positionAtElement.width() - _wrapper.width()) / 2);
						_calc.delta = -_options.deltaPosition;
					} else if (_options.deltaDirection.match(/^left$/i)) {
						_calc.top = offset.top + _options.offsetTop + ((_options.positionAtElement.height() - _wrapper.height()) / 2);
						_calc.left = offset.left + _options.offsetLeft - _wrapper.width();
						_calc.delta = _options.deltaPosition;
					} else if (_options.deltaDirection.match(/^right$/i)) {
						_calc.top = offset.top + _options.offsetTop + ((_options.positionAtElement.height() - _wrapper.height()) / 2);
						_calc.left = offset.left + _options.positionAtElement.width() + _options.offsetLeft;
						_calc.delta = -_options.deltaPosition;
					}
				} else if (_options.positionAt.match(/^body$/i)) {
					if (_options.deltaDirection.match(/^up|left$/i)) {
						_calc.top = _options.offsetTop;
						_calc.left = _options.offsetLeft;
						// up or left
						_calc.delta = _options.deltaPosition;
					} else {
						if (_options.deltaDirection.match(/^down$/i)) {
							_calc.top = parseInt(_options.offsetTop + _wrapper.height());
							_calc.left = _options.offsetLeft;
						} else {
							_calc.top = _options.offsetTop;
							_calc.left = parseInt(_options.offsetLeft + _wrapper.width());
						}
						// down or right
						_calc.delta = -_options.deltaPosition;
					}
				} else if (_options.positionAt.match(/^mouse$/i)) {
					if (_options.deltaDirection.match(/^up|left$/i)) {
						if (_options.deltaDirection.match(/^up$/i)) {
							_calc.top = -(_options.offsetTop + _wrapper.height());
							_calc.left = _options.offsetLeft;
						} else if (_options.deltaDirection.match(/^left$/i)) {
							_calc.top = _options.offsetTop;
							_calc.left = -(_options.offsetLeft + _wrapper.width());
						}
						// up or left
						_calc.delta = _options.deltaPosition;
					} else {
						_calc.top = _options.offsetTop;
						_calc.left = _options.offsetLeft;
						// down or right
						_calc.delta = -_options.deltaPosition;
					}
				}
				// hide
				_wrapper.hide();
				// handle the wrapper (element|body) positioning
				if (_options.positionAt.match(/^element|body$/i)) {
					_wrapper.css({
						'position': 'absolute',
						'top': _calc.top + 'px',
						'left': _calc.left + 'px'
					});
				}
			};
			return this;
		}
	});
})(jQuery);